package com.laker.autotest.core;

import cn.hutool.core.text.CharSequenceUtil;
import com.laker.autotest.config.EasyConfig;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.options.AriaRole;
import com.microsoft.playwright.options.WaitForSelectorState;
import com.microsoft.playwright.options.WaitUntilState;

public abstract class BasePage {
    protected final EasyConfig config = EasyConfigFactory.getConfig();
    protected final Page page;

    public BasePage(Page initPage) {
        this.page = initPage;
        if (CharSequenceUtil.isNotBlank(pageUrl())) {
            this.navigateTo(pageUrl());
        }
    }

    protected abstract String pageUrl();

    /**
     * 导航到url
     */
    public void navigateTo(String url) {
        page.navigate(url, new Page.NavigateOptions()
                // 指定在导航到一个新页面或等待导航完成时的行为
                // COMMIT：该状态可能指的是等待浏览器提交新的文档，即在导航请求被发送和新页面开始加载之间的瞬间。这个状态可能不常见，因为它表示的是一个非常早期的导航阶段，这个阶段可能并不适合执行大多数测试或自动化脚本的操作。
                // DOMCONTENTLOADED：等待 DOMContentLoaded 事件触发，这意味着HTML文档已经完全加载和解析，但样式表、图片和子框架可能尚未完全加载。
                // LOAD：等待 load 事件触发，这意味着页面上的所有资源（包括图片、样式表和子框架）都已加载完成。
                // NETWORKIDLE：等待网络空闲，即在一定时间内没有任何网络请求。
                .setWaitUntil(WaitUntilState.DOMCONTENTLOADED));
    }


    // ----------- 获取组件 通过ID或者Class等 -----------
    // 建议优先考虑面向用户可见的属性和显式契约，因为我们模拟的就是用户眼睛看到的属性，例如什么名称和按钮啊等组合

    /**
     * <p>
     * ID选择器(#id)
     * 优先用这个选择器，因为它能保障是唯一的原始。
     * 示例：
     * <button id="toolbar-search-button"><i></i><span>搜索</span></button>
     * getComponentById("toolbar-search-button")
     * </p>
     * ID选择器(#id)
     *
     * @param id
     * @return
     */
    protected Locator getComponentById(String id) {
        return page.locator("#" + id);
    }

    /**
     * <p>
     * 类选择器(.class)
     * 示例：
     * <button class="submit">搜索</button>
     * getComponentByClassName("submit")
     * </p>
     *
     * @param className
     * @return
     */
    protected Locator getComponentByClassName(String className) {
        return page.locator("." + className);
    }

    /**
     * <p>
     * 定位具有特定角色的元素
     * 示例：
     * <button role="button">Click me</button>
     * getComponentByRole(AriaRole.BUTTON,"Click me")
     * </p>
     *
     * @param role
     * @return
     */
    protected Locator getComponentByRole(AriaRole role, String name) {
        return page.getByRole(role, new Page.GetByRoleOptions().setName(name));
    }

    /**
     * <p>
     * 根据文本内容定位元素
     * 示例：
     *     <div>Hello, World!</div>
     *     getComponentByText("Hello, World!")
     * </p>
     *
     * @param text
     * @return
     */
    protected Locator getComponentByText(String text) {
        return page.getByText(text);
    }

    /**
     * <p>
     * 根据关联标签的文本内容定位表单控件
     * 示例：
     * <label for="username">Username:</label>
     * <input type="text" id="username">
     * getComponentByLabel("Username:")
     * </p>
     *
     * @param label
     * @return
     */
    protected Locator getComponentByLabel(String label) {
        return page.getByLabel(label);
    }

    /**
     * <p>
     * 根据输入框的占位符定位输入框
     * 示例：
     * <input type="text" placeholder="Enter your name">
     * getComponentByPlaceholder("Enter your name")
     * </p>
     *
     * @param placeholder
     * @return
     */
    protected Locator getComponentByPlaceholder(String placeholder) {
        return page.getByPlaceholder(placeholder);
    }

    /**
     * <p>
     * 根据图像的文本替代内容定位元素
     * 示例：
     * <img src="image.jpg" alt="Beautiful landscape">
     * getComponentByAltText("Beautiful landscape")
     * </p>
     *
     * @param altText
     * @return
     */
    protected Locator getComponentByAltText(String altText) {
        return page.getByAltText(altText);
    }

    /**
     * <p>
     * 根据元素的标题属性定位元素
     * 示例：
     *     <div title="Important information">...</div>
     *     getComponentByTitle("Important information")
     * </p>
     *
     * @param title
     * @return
     */
    protected Locator getComponentByTitle(String title) {
        return page.getByTitle(title);
    }

    /**
     * <p>
     * 根据元素的 data-testid 属性定位元素
     * 示例：
     * <div data-testid="user-profile">...</div>
     * getComponentByTestId("user-profile")
     * </p>
     *
     * @param testId
     * @return
     */
    protected Locator getComponentByTestId(String testId) {
        return page.getByTestId(testId);
    }


    /**
     * <p>
     * CSS选择器
     * 示例：
     * <a class="ui button star"
     * getComponentByCssSelector(".ui.button.star")
     * </p>
     *
     * @param cssSelector
     * @return
     */
    protected Locator getComponentByCssSelector(String cssSelector) {
        return page.locator("css=" + cssSelector);
    }

    // ----------- 组件操作 获取组件文本内容等 -----------

    /**
     * <p>
     * 获取元素文本内容
     * innerText获取的是元素渲染后的文本内容，类似于用户在浏览器中看到的内容。
     * 它受CSS样式的影响，例如，如果文本通过CSS被设置为不可见（例如，通过display: none或visibility: hidden），那么innerText不会包含这些文本。
     * innerText合并了文本节点，并移除了额外的空格和换行符，以更接近实际显示的文本格式。
     * 简而言之，innerText返回的是更加“人类可读”的版本，它考虑了样式和元素的可见性，而textContent返回的是元素的完整文本内容，包括隐藏和不可见的文本，它不考虑样式或元素的可见性。
     * </p>
     *
     * @param element
     * @return
     */
    protected String getComponentText(Locator element) {
        // 在innerText() 方法被调用时页面元素可能还没有完成更新。
        // 通过使用 waitFor 方法等待元素的状态变为可见，以确保当你尝试获取文本内容时，消息已经被正确地加载和渲染在页面上。
        // 这是自动化测试中常用的一种同步策略，用以确保页面状态与代码执行的同步。
        element.waitFor(new Locator.WaitForOptions()
                // "attached" - 等待元素出现在DOM中。使用这个选项时，代码会等待直到目标元素在文档对象模型（DOM）中被检测到。
                // "detached" - 等待元素不再出现在DOM中。当你想要等待某个元素从DOM中移除时，可以使用这个选项。
                // "visible" -（默认） 等待元素有一个非空的边界框，且没有被设置为 visibility:hidden。
                //             请注意，没有任何内容或者被设置为 display:none 的元素会有一个空的边界框，并且不被认为是可见的。
                // "hidden" - 等待元素要么从DOM中分离，要么有一个空的边界框，或者设置为 visibility:hidden。这是与 "visible" 选项相反的状态。
                .setState(WaitForSelectorState.VISIBLE)
                .setTimeout(config.getContextOptions().timeout));
        return element.innerText();
    }

    /**
     * <p>
     * 填充元素
     * - 普通文本 Peter
     * - 时间 2020-02-02 或 13-15 或 2020-03-02T05:15
     * </p>
     *
     * @param element
     * @param value
     */
    protected void fill(Locator element, String value) {
        element.fill(value);
    }

}
